package directory

import (
	"errors"
	"fmt"
	"github.com/containerd/btrfs"
	"github.com/gogf/gf/errors/gerror"
	"github.com/shirou/gopsutil/v3/disk"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/TerraMaster/tos-modules/database/sqlitemodel"
	"gitlab.local/TerraMaster/tos-modules/directory"
	"gitlab.local/golibrary/linux"
	"gitlab.local/golibrary/utils"
	"os"
	"path/filepath"
	"strings"
	"time"
	"unicode"
)

// PassphrasePath 文件夹秘钥临时路径
var PassphrasePath = filepath.Join(os.TempDir(), "folder_key", "%s.key")

func (d *Directory) Create(user string, folder directory.CreateFolder) (string, error) {
	folderName := folder.FolderName
	var exists int64
	db := d.dbGetter.DbGet().Table("share")
	_ = db.Where("foldername=?", folder.FolderName).Count(&exists)
	if exists > 0 {
		return "", errors.New("folder name exists")
	}
	if folder.Type == "" {
		folder.Type = "LFS"
		folder.MntPath = filepath.Join("/", folder.Device, folderName)
	}
	folder.Owner = user
	if err := db.Create(&folder.Share).Error; err != nil {
		return "", gerror.Wrap(err, "Create")
	}

	if folder.Ecryptfs == 1 {
		dfs, err := disk.Partitions(false)
		if err != nil {
			return "", err
		}
		// hyper-lock 卷不能创建加密文件夹
		for _, df := range dfs {
			if df.Device == folder.Device && df.Fstype == "xfs" {
				return "", errors.New("hyper-lock volume cannot create encrypt-folder")
			}
		}
		folder.MntPath = filepath.Join("/", folder.Device, fmt.Sprintf("@%s@", folder.FolderName))
	}
	//_ = os.Chmod(filepath.Dir(folder.MntPath), 0770)
	if folder.Type == "LFS" {
		deviceInfo := linux.GetDeviceInfoByPath(filepath.Dir(folder.MntPath))
		if deviceInfo == nil {
			return "", errors.New("invalid volume")
		}
		if deviceInfo.Filesystem == "btrfs" {
			_ = btrfs.SubvolCreate(folder.MntPath)
			if folder.QuotaFlag == 1 && folder.Quota > 0 {
				err := d.setShareFolderQuota(folder.MntPath, folder.Quota)
				if err != nil {
					data := map[string]interface{}{
						"quota":      0,
						"quota_flag": -1,
					}
					db.Where("folder_id=?", folder.FolderId).Updates(data)
				}
			}
		} else {
			_ = utils.MkdirPerm(folder.MntPath, os.ModePerm)
		}
		if !utils.Exists(folder.MntPath) {
			return "", errors.New("folder create failed")
		}
		_ = os.Chmod(folder.MntPath, 0770)
	} else {
		_ = utils.MkdirPerm(folder.MntPath, os.ModePerm)
	}
	return folder.MntPath, nil
}

func (d *Directory) Get() (result []sqlitemodel.Share) {
	return d.GetByType("LFS")
}

func (d *Directory) GetByType(cate string) (result []sqlitemodel.Share) {
	result = make([]sqlitemodel.Share, 0)
	db := d.dbGetter.DbGet()
	tx := db.Table("share").Where("type=? and device <> ?", cate, "@usb")
	//尝试读取3次，每次间隔1秒
	maxTry := 0
	for {
		err := tx.Find(&result).Error
		if err == nil || maxTry >= 3 {
			break
		}
		maxTry++
		time.Sleep(time.Second)
	}
	return
}

func (d *Directory) getFolderByID(id int) (*sqlitemodel.Share, error) {
	db := d.dbGetter.DbGet()
	tx := db.Table("share").Where("folder_id=?", id)
	var count int64
	tx.Count(&count)
	if count == 0 {
		return nil, errors.New("invalid folder id")
	}
	result := sqlitemodel.Share{}
	tx.Find(&result)
	return &result, nil
}

func (d *Directory) Set(id int, changes directory.ModifyFolder) (string, error) {
	info, err := d.getFolderByID(id)
	if err != nil {
		return "", err
	}
	if info.FolderName != changes.FolderName {
		//目录改名，后续可优化此处
		_, _ = utils.ShellExec(fmt.Sprintf("/etc/tos/scripts/folder_rename %d '%s' >/dev/null", id, changes.FolderName))
		newPath := filepath.Join(filepath.Dir(info.MntPath), changes.FolderName)
		if utils.Exists(newPath) {
			changes.MntPath = newPath
		}
	}
	data := map[string]interface{}{
		"foldername":  changes.Share.FolderName,
		"oplock":      changes.Share.OpLock,
		"hidden":      changes.Share.Hidden,
		"description": changes.Share.Description,
		"recycle":     changes.Share.Recycle,
		"savedays":    changes.Share.SaveDays,
		"quota":       changes.Share.Quota,
	}
	if changes.MntPath != "" {
		data["mntpath"] = changes.MntPath
	}
	//加密文件夹特殊处理
	if info.Ecryptfs == 1 {
		info.MntPath = filepath.Join("/", info.Device, fmt.Sprintf("@%s@", info.FolderName))
	}
	// 如果配额标志开启,且新设置的配额大于当前配额，才可更新。
	if info.QuotaFlag == 1 && changes.Quota > info.Quota {
		err := d.setShareFolderQuota(info.MntPath, changes.Quota)
		if err != nil {
			data["quota"] = 0
			data["quota_flag"] = -1
		}
	}
	_ = d.dbGetter.DbGet().Model(&sqlitemodel.Share{}).Where("folder_id=?", id).Updates(data)
	if changes.MntPath == "" {
		changes.MntPath = info.MntPath
	}
	if info.Ecryptfs == 1 {
		changes.MntPath = filepath.Join(filepath.Dir(changes.MntPath), fmt.Sprintf("@%s@", changes.FolderName))
	}
	return changes.MntPath, nil
}

func (d *Directory) Del(id int) error {
	tx := d.dbGetter.DbGet().Table("share")
	var info sqlitemodel.Share
	_ = tx.Where(id).Find(&info)
	go d.clearFolder(info)
	_ = tx.Where(id).Delete(sqlitemodel.Share{})
	return nil
}

func (d *Directory) GetFolder(id int) (result sqlitemodel.Share) {
	d.dbGetter.DbGet().Table("share").Where(id).First(&result)
	return
}

func (d *Directory) clearFolder(info sqlitemodel.Share) {
	umount := false
	if info.Ecryptfs == 1 {
		for i := 0; i < 5; i++ {
			if linux.IsMount(info.MntPath) {
				_, _ = utils.ShellExec(fmt.Sprintf("fuser -k -KILL %s", info.MntPath))
				_, _ = utils.ShellExec(fmt.Sprintf("umount %s", info.MntPath))
			} else {
				umount = true
				break
			}
			time.Sleep(time.Second)
		}
	} else {
		umount = true
	}
	if umount {
		//判断是否是btrfs子卷，如果是则使用btrfs删除
		if btrfs.IsSubvolume(info.MntPath) == nil {
			_ = btrfs.SubvolDelete(info.MntPath)
		} else {
			_ = os.RemoveAll(info.MntPath)
		}
		_ = os.Remove(filepath.Join("/mnt", info.FolderName))
		if info.Ecryptfs == 1 {
			_ = os.RemoveAll(fmt.Sprintf("/%s/@%s@", info.Device, info.FolderName))
		}
	}
}

// setShareFolderQuota set the size of shared folders
func (d *Directory) setShareFolderQuota(path string, quota int) interface{} {
	quotaSize := fmt.Sprintf("%dG", quota)
	if quota < 0 {
		quotaSize = "none"
	}
	// check is btrfs quota is enabled
	_, err := utils.ShellExec(fmt.Sprintf("btrfs qgroup show %s", path))
	if err != nil {
		return errors.New(fmt.Sprintf("%s ! quota not enabled", path))
	}
	// set quota size
	_, err = utils.ShellExec(fmt.Sprintf("btrfsctl --resize --quota %s --path %s", quotaSize, path))
	if err != nil {
		return errors.New(fmt.Sprintf("%s ! btrfsctl not resize --quota", path))
	}
	return nil
}

// EcryptfsMount 加密文件夹挂载
func (d *Directory) EcryptfsMount(path, pwd string) error {
	dir, base := filepath.Split(path)
	path = filepath.Join(dir, strings.Trim(base, "@")) //去除首尾多余@符号
	mount, e := d.FolderIsMount(path)
	if e != nil {
		return gerror.Wrap(e, "EcryptfsMount")
	}
	if mount {
		return nil
	}
	res, e := utils.ShellExec(fmt.Sprintf(`%s mount '%s' '%s'`, constant.EcryptfsMount, path, pwd))
	if e != nil {
		return gerror.Wrap(e, "EcryptfsMount1")
	}
	if !strings.Contains(res, "successful!") { //挂载失败
		return errors.New(res)
	}
	return nil
}

// EcryptfsUmount 加密文件夹卸载
func (d *Directory) EcryptfsUmount(path string) error {
	dir, base := filepath.Split(path)
	path = filepath.Join(dir, strings.Trim(base, "@")) //去除首尾多余@符号
	mount, e := d.FolderIsMount(path)
	if e != nil {
		return gerror.Wrap(e, "EcryptfsUmount0")
	}
	if !mount {
		return nil
	}
	if _, e = utils.ShellExec(fmt.Sprintf(`%s umount '%s'`, constant.EcryptfsMount, path)); e != nil {
		return gerror.Wrap(e, "EcryptfsUmount2")
	}
	return nil
}

// FolderIsMount 文件夹是否挂载
func (d *Directory) FolderIsMount(path string) (bool, error) {
	res, e := utils.ShellExec(fmt.Sprintf(`df-json | grep '%s$' > /dev/null;echo $?`, path))
	if e != nil {
		return false, gerror.Wrap(e, "FolderIsMount")
	}
	return strings.TrimSpace(res) == "0", nil
}

// ObtainSecretKey 获取秘钥文件
// return 秘钥文件临时路径 1为密码错误 错误信息
func (d *Directory) ObtainSecretKey(path, pwd string) (string, uint8, error) {
	dir, base := filepath.Split(path)
	path = filepath.Join(dir, strings.Trim(base, "@")) //去除首尾多余@符号
	if !utils.Exists(path) {
		return "", 0, errors.New(fmt.Sprintf("%s there is no", path))
	}
	for _, v := range pwd {
		if !unicode.IsPrint(v) {
			return "", 0, errors.New(fmt.Sprintf("pwd can't contain special characters:%+v", v))
		}
	}

	passphrase := fmt.Sprintf(PassphrasePath, filepath.Base(path))
	_ = os.Mkdir(filepath.Dir(passphrase), 0744)
	res, e := utils.ShellExec(fmt.Sprintf(`ecryptfs-tools -action=ecrypt -path='%s' -passphrase='%s' -passphrase-file='%s'`, path, pwd, passphrase))
	if e != nil {
		return "", 0, gerror.Wrap(e, "ExportSecretKey")
	}
	if strings.Contains(res, "passphrase is error") {
		return "", 1, nil
	}
	if strings.Contains(res, "Cipher generated successfully") {
		return passphrase, 0, nil
	}
	return "", 0, errors.New("	export the error")
}
